package cn.threewaters.qtyy.util.hutool.setting.dialect;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.util.Properties;

import cn.threewaters.qtyy.util.hutool.core.convert.Convert;
import cn.threewaters.qtyy.util.hutool.core.getter.BasicTypeGetter;
import cn.threewaters.qtyy.util.hutool.core.getter.OptBasicTypeGetter;
import cn.threewaters.qtyy.util.hutool.core.io.FileUtil;
import cn.threewaters.qtyy.util.hutool.core.io.IoUtil;
import cn.threewaters.qtyy.util.hutool.core.io.resource.ClassPathResource;
import cn.threewaters.qtyy.util.hutool.core.io.resource.UrlResource;
import cn.threewaters.qtyy.util.hutool.core.io.watch.SimpleWatcher;
import cn.threewaters.qtyy.util.hutool.core.io.watch.WatchMonitor;
import cn.threewaters.qtyy.util.hutool.core.lang.Assert;
import cn.threewaters.qtyy.util.hutool.core.util.CollectionUtil;
import cn.threewaters.qtyy.util.hutool.core.util.StrUtil;
import cn.threewaters.qtyy.util.hutool.setting.SettingRuntimeException;
import lombok.extern.slf4j.Slf4j;

/**
 * Properties文件读取封装类
 * 
 * @author loolly
 */
@Slf4j
public final class Props extends Properties implements BasicTypeGetter<String>, OptBasicTypeGetter<String> {
	private static final long serialVersionUID = 1935981579709590740L;

	// -----------------------------------------------------------------------
	// 私有属性 start
	/** 属性文件的URL */
	private URL propertiesFileUrl;
	private WatchMonitor watchMonitor;
	// -----------------------------------------------------------------------
	// 私有属性 end

	/**
	 * 获得Classpath下的Properties文件
	 * 
	 * @param resource
	 *            资源（相对Classpath的路径）
	 * @return Properties
	 */
	public static Properties getProp(String resource) {
		return new Props(resource);
	}

	// -----------------------------------------------------------------------
	// 构造方法 start
	/**
	 * 构造
	 */
	public Props() {
		super();
	}

	/**
	 * 构造，使用相对于Class文件根目录的相对路径
	 * 
	 * @param pathBaseClassLoader
	 *            相对路径（相对于当前项目的classes路径）
	 */
	public Props(String pathBaseClassLoader) {
		Assert.notBlank(pathBaseClassLoader, "Blank properties file path !");
		this.load(new ClassPathResource(pathBaseClassLoader));
	}

	/**
	 * 构造
	 * 
	 * @param propertiesFile
	 *            配置文件对象
	 */
	public Props(File propertiesFile) {
		Assert.notNull(propertiesFile, "Null properties file!");
		this.load(new UrlResource(propertiesFile));
	}

	/**
	 * 构造，相对于classes读取文件
	 * 
	 * @param path
	 *            相对路径
	 * @param clazz
	 *            基准类
	 */
	public Props(String path, Class<?> clazz) {
		Assert.notBlank(path, "Blank properties file path !");
		this.load(new ClassPathResource(path, clazz));
	}

	/**
	 * 构造，使用URL读取
	 * 
	 * @param propertiesUrl
	 *            属性文件路径
	 */
	public Props(URL propertiesUrl) {
		Assert.notNull(propertiesUrl, "Null properties URL !");
		this.load(new UrlResource(propertiesUrl));
	}

	/**
	 * 构造，使用URL读取
	 * 
	 * @param properties
	 *            属性文件路径
	 */
	public Props(Properties properties) {
		if (CollectionUtil.isNotEmpty(properties)) {
			this.putAll(properties);
		}
	}

	// -----------------------------------------------------------------------
	// 构造方法 end

	/**
	 * 初始化配置文件
	 * 
	 * @param urlResource
	 *            {@link UrlResource}
	 */
	public void load(UrlResource urlResource) {
		this.propertiesFileUrl = urlResource.getUrl();
		if (null == this.propertiesFileUrl) {
			throw new SettingRuntimeException("Can not find properties file: [{}]", urlResource);
		}
		log.debug("Load properties [{}]", propertiesFileUrl.getPath());
		InputStream in = null;
		try {
			in = urlResource.getStream();
			super.load(in);
		} catch (Exception e) {
			log.error(e.getMessage(), "Load properties error!");
		} finally {
			IoUtil.close(in);
		}
	}

	/**
	 * 重新加载配置文件
	 */
	public void load() {
		this.load(new UrlResource(this.propertiesFileUrl));
	}

	/**
	 * 在配置文件变更时自动加载
	 * 
	 * @param autoReload
	 *            是否自动加载
	 */
	public void autoLoad(boolean autoReload) {
		if (autoReload) {
			if (null != this.watchMonitor) {
				this.watchMonitor.close();
				try {
					watchMonitor = WatchMonitor.create(Paths.get(this.propertiesFileUrl.toURI()));
					watchMonitor.setWatcher(new SimpleWatcher() {
						@Override
						public void onModify(WatchEvent<?> event) {
							load();
						}
					}).start();
				} catch (Exception e) {
					throw new SettingRuntimeException(e, "Setting auto load not support url: [{}]",
							this.propertiesFileUrl);
				}
			}
		} else {
			IoUtil.close(this.watchMonitor);
			this.watchMonitor = null;
		}
	}

	// -----------------------------------------------------------------------
	// Get start
	@Override
	public Object getObj(String key, Object defaultValue) {
		return getStr(key, null == defaultValue ? null : defaultValue.toString());
	}

	@Override
	public Object getObj(String key) {
		return getObj(key, null);
	}

	@Override
	public String getStr(String key, String defaultValue) {
		return super.getProperty(key, defaultValue);
	}

	@Override
	public String getStr(String key) {
		return super.getProperty(key);
	}

	@Override
	public Integer getInt(String key, Integer defaultValue) {
		return Convert.toInt(getStr(key), defaultValue);
	}

	@Override
	public Integer getInt(String key) {
		return getInt(key, null);
	}

	@Override
	public Boolean getBool(String key, Boolean defaultValue) {
		return Convert.toBool(getStr(key), defaultValue);
	}

	@Override
	public Boolean getBool(String key) {
		return getBool(key, null);
	}

	@Override
	public Long getLong(String key, Long defaultValue) {
		return Convert.toLong(getStr(key), defaultValue);
	}

	@Override
	public Long getLong(String key) {
		return getLong(key, null);
	}

	@Override
	public Character getChar(String key, Character defaultValue) {
		final String value = getStr(key);
		if (StrUtil.isBlank(value)) {
			return defaultValue;
		}
		return value.charAt(0);
	}

	@Override
	public Character getChar(String key) {
		return getChar(key, null);
	}

	@Override
	public Float getFloat(String key) {
		return getFloat(key, null);
	}

	@Override
	public Float getFloat(String key, Float defaultValue) {
		return Convert.toFloat(getStr(key), defaultValue);
	}

	@Override
	public Double getDouble(String key, Double defaultValue) throws NumberFormatException {
		return Convert.toDouble(getStr(key), defaultValue);
	}

	@Override
	public Double getDouble(String key) throws NumberFormatException {
		return getDouble(key, null);
	}

	@Override
	public Short getShort(String key, Short defaultValue) {
		return Convert.toShort(getStr(key), defaultValue);
	}

	@Override
	public Short getShort(String key) {
		return getShort(key, null);
	}

	@Override
	public Byte getByte(String key, Byte defaultValue) {
		return Convert.toByte(getStr(key), defaultValue);
	}

	@Override
	public Byte getByte(String key) {
		return getByte(key, null);
	}

	@Override
	public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
		final String valueStr = getStr(key);
		if (StrUtil.isBlank(valueStr)) {
			return defaultValue;
		}

		try {
			return new BigDecimal(valueStr);
		} catch (Exception e) {
			return defaultValue;
		}
	}

	@Override
	public BigDecimal getBigDecimal(String key) {
		return getBigDecimal(key, null);
	}

	@Override
	public BigInteger getBigInteger(String key, BigInteger defaultValue) {
		final String valueStr = getStr(key);
		if (StrUtil.isBlank(valueStr)) {
			return defaultValue;
		}

		try {
			return new BigInteger(valueStr);
		} catch (Exception e) {
			return defaultValue;
		}
	}

	@Override
	public BigInteger getBigInteger(String key) {
		return getBigInteger(key, null);
	}

	@Override
	public <E extends Enum<E>> E getEnum(Class<E> clazz, String key, E defaultValue) {
		return Convert.toEnum(clazz, getStr(key), defaultValue);
	}

	@Override
	public <E extends Enum<E>> E getEnum(Class<E> clazz, String key) {
		return getEnum(clazz, key, null);
	}

	// -----------------------------------------------------------------------
	// Get end

	// -----------------------------------------------------------------------
	// Set start
	/**
	 * 设置值，无给定键创建之。设置后未持久化
	 * 
	 * @param key
	 *            属性键
	 * @param value
	 *            属性值
	 */
	public void setProperty(String key, Object value) {
		super.setProperty(key, value.toString());
	}

	/**
	 * 持久化当前设置，会覆盖掉之前的设置
	 * 
	 * @param absolutePath
	 *            设置文件的绝对路径
	 */
	public void store(String absolutePath) {
		try {
			FileUtil.touch(absolutePath);
			super.store(FileUtil.getOutputStream(absolutePath), null);
		} catch (FileNotFoundException e) {
			// 不会出现这个异常
		} catch (IOException e) {
			log.error(e.getMessage(), "Store properties to [{}] error!", absolutePath);
		}
	}

	/**
	 * 存储当前设置，会覆盖掉以前的设置
	 * 
	 * @param path
	 *            相对路径
	 * @param clazz
	 *            相对的类
	 */
	public void store(String path, Class<?> clazz) {
		this.store(FileUtil.getAbsolutePath(path, clazz));
	}
	// -----------------------------------------------------------------------
	// Set end
}
